package server;

import java.util.Random;
import java.util.Vector;

import requestEnvelopes.agent.ReceiveEvent;
import server.interfaces.IDispatcher;
import server.interfaces.IMobileAgentCommunicationChannelSender;
import server.interfaces.IMobileAgentExecutionEngine;
import server.interfaces.IMobileAgentMigrationManager;
import server.interfaces.IMobileAgentNaming;
import server.interfaces.ISensorBoardManager;
import server.interfaces.ITimerManager;
import agent.IAgentProxy;

import com.CharNotValidException;
import com.Constants;
import com.Event;
import com.LedsManager;
import com.ParamsLabel;
import com.sun.spot.interisolate.InterIsolateServer;
import com.sun.spot.interisolate.ReplyEnvelope;
import com.sun.spot.interisolate.RequestSender;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.resources.transducers.LEDColor;
import com.sun.spot.util.IEEEAddress;
import com.sun.squawk.Isolate;
import com.sun.squawk.VM;
import com.sun.squawk.io.mailboxes.NoSuchMailboxException;

public class MobileAgentExecutionEngine implements IMobileAgentExecutionEngine {
		
	private Vector agents; 
	private IEEEAddress myIEEEAddress;
	private String executionEngineName; 
	private Vector proxy;
	
	//Components
	private IMobileAgentMigrationManager migrationManager;
	private IMobileAgentCommunicationChannelSender communicationChannelSender;
	//private IMobileAgentCommunicationChannelReceiver communicationChannelReceiver;
	private IDispatcher dispatcher;
	private IMobileAgentNaming naming;
	private ITimerManager timerManager;
	private ISensorBoardManager sensorManager;
	private ISensorBoardManager ioManager;
	
	/**
	 * Publish itself inside the WSN 
	 */
	private void publishMySelf() {
		
		InterIsolateServer.run(this.executionEngineName, this);
		Event event = new Event (this.myIEEEAddress.asDottedHex(), Constants.BROADCAST, Event.DSC_PUBLISH, Event.NOW);
		this.communicationChannelSender.send(event);
		
	}
	
	/**
	 * Creates a MobileAgentsExecutionEngine with the specified communicationPort
	 * @param communicationPort
	 */
	public MobileAgentExecutionEngine(String communicationPort) {

		this.executionEngineName = "AGENT_SERVER";
		this.agents = new Vector ();
		this.proxy = new Vector();
		
        this.myIEEEAddress 					= new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());
		this.migrationManager 				= Factory.getIMobileAgentMigrationManager(this);
		this.communicationChannelSender 	= Factory.getIMobileAgentCommunicationChannelSender(this, communicationPort);
											  Factory.getIMobileAgentCommunicationChannelReceiver(this, this.communicationChannelSender, communicationPort);
		this.dispatcher 					= Factory.getIDispatcher(this);
		this.naming							= Factory.getIMobileAgentNaming(this);
		this.timerManager					= Factory.getITimerManager(this);
		
		this.sensorManager					= Factory.getSensorManager(this);
		this.sensorManager.addResource(Factory.getTemperatureSensor(this.sensorManager));
		this.sensorManager.addResource(Factory.getLightSensor(this.sensorManager));
		this.sensorManager.addResource(Factory.getAcceleratorSensor(this.sensorManager));
		
		this.ioManager						= Factory.getIOManager(this);
		this.ioManager.addResource(Factory.getSwitchInput(this.ioManager));
		this.ioManager.addResource(Factory.getLedActuator(this.ioManager));
		this.ioManager.addResource(Factory.getFlashManager(this.ioManager));
		this.ioManager.addResource(Factory.getBatteryManager(this.ioManager));
		
		this.publishMySelf();
		
	}
	
	public void migrate(String sourceMA, String dest) {
		proxy.addElement(Factory.getIAgentProxy(sourceMA, dest));		
		migrationManager.addAgentToMigrate(sourceMA, dest);
	}	

	
	public void clone(String sourceMA, String maID, String nodeID) {
		// TODO Auto-generated method stub
		
	}
	
	public boolean create(String sourceMA, String agent,
			String [] params, String nodeID) {
		
		return create(this.generateAgentSuffix(), sourceMA, agent, params, nodeID);
	}

	public boolean create(String agentSuffix, String sourceMA, String agent,
			String [] params, String nodeID) {
		//Local node
		if (nodeID.equals(this.getMyIEEEAddress().asDottedHex())) {
			
			String agentID;
			try {
				agentID = this.generateAgentID(agentSuffix);
			}
			catch (AgentIDException e) {
				return false;
			}
			
			String uri = VM.getCurrentIsolate().getParentSuiteSourceURI();
			int paramsLength = (params != null)? params.length: 0;
	
			String [] isolateParams = new String [paramsLength+2];
			
			isolateParams[0] = agentID;
			isolateParams[1] = this.executionEngineName;
			for (int i = 0; i < paramsLength; i++)
				isolateParams[i+2] = params[i];		
			Isolate agentIsolate = new Isolate(agent, isolateParams, null, uri);
			this.addAgent(agentIsolate);
			agentIsolate.start();
			if (!agentID.equals("")) {
				Event agentIDEvent = new Event ("", sourceMA, Event.AGN_ID, Event.NOW);
				try {
					agentIDEvent.setParam(ParamsLabel.AGT_ID, agentID);
				}
				catch (CharNotValidException e) {
					LedsManager.error(LEDColor.CYAN);
					e.printStackTrace();
				}
				this.send("", sourceMA, agentIDEvent, false);
			}
			return true;
			
		}//Remote node
		else {
			try {
				Event remoteCreation = new Event (sourceMA, this.executionEngineName, Event.AGN_CREATION, Event.NOW);
				
				remoteCreation.setParam(ParamsLabel.AGT_SUFFIX, agentSuffix);
				remoteCreation.setParam(ParamsLabel.AGT_NAME, agent);
				String isolateParams = "";
				int paramsLength = (params != null)? params.length: 0;
				for (int i = 0; i < paramsLength; i++) {
					isolateParams += params[i] + Event.VECTOR_PARAM_SEPARATOR;
				}
				remoteCreation.setParam(ParamsLabel.AGT_PARAMS, isolateParams);
				remoteCreation.setParam(ParamsLabel.AGT_ADDRESS, nodeID);
				this.communicationChannelSender.send(remoteCreation);
				return true;
			}
			catch (CharNotValidException e) {
				LedsManager.error(LEDColor.CYAN);
				e.printStackTrace();
				return false;
			}
		}
			
	}
	
	/**
	 * Generate a pseudorandom number between 0 (inclusive) and the specified value (Constants.MAX_AGENT_ID) 
	 * @return a pseudorandom number
	 */
	public String generateAgentSuffix () {
		
		Random randomGen = new Random();
		return "" + randomGen.nextInt(Constants.MAX_AGENT_ID);
		
	}

	
	public String generateAgentID(String agentSuffix) throws AgentIDException {
		
		if (agentSuffix.length() > 6)
			throw new AgentIDException("Suffix is too long. Max 6 characters allowed");
		
		String ieee = this.myIEEEAddress.asDottedHex();
		String agentID = ieee.substring(10);
		agentID += agentSuffix;
		
		Isolate localAgent = this.getAgent(agentID);
		IAgentProxy proxyAgent = this.getAgentProxy(agentID);
		if (proxyAgent != null || localAgent != null)
			throw new AgentIDException("Duplicate agent name");
		
		return agentID;
	}

	public Vector getMobileAgents() {
		return this.agents;
	}

	public Vector getLocalAgentsID() {
		Vector localAgentsID = new Vector(this.agents.size());
		for (int i = 0; i < this.agents.size(); i++) {
			localAgentsID.addElement(
					((Isolate)this.agents.elementAt(i)).getMainClassArguments()[0]);
		}
		return localAgentsID;
	}
	
	public Vector getNeighbors() {
		return this.naming.getNeighborsAddress();
	}


	public void send(String sourceMA, String targetMA, Event message, boolean local) {
		
		//Future Works: Multicast Management
		
		/*
		 * Method organization:
		 * if(broadcast message)
		 * 		if(local message) send message to local spot agents
		 * 		else send message to everyone
		 * else if(destination is BASESTATION) send message to Basestation 
		 * else
		 * 		if(destination agent is in my agent queue) send message to my dispatcher
		 * 		if(destination agent is in my proxy queue) send message to agent proxy
		 * 		
		 * 		if(destination agent is unknown) ask for agent to NamingManager and 
		 * 											send message to the right node
		 * 		
		 * 		if(destination agent is unreachable) send message to sourceAgent (NACK)
		 */
		
		//Search Agent
		if (targetMA.equals(Constants.BROADCAST)) {
			if (!local) {
				/*
				 * The event is sent only to Local Agents and Remote Agents,
				 * NOT Proxy.
				 */
				Vector neighbors = this.naming.getNeighborsAddress();
				for (int i = 0; i < neighbors.size(); i++) {
					try {
						String address = (String)neighbors.elementAt(i);
						message.setParam(ParamsLabel.EVT_ADDRESS, address);
						this.communicationChannelSender.send(message);
					}
					catch (CharNotValidException e) {
						LedsManager.error(LEDColor.CYAN);
						e.printStackTrace();
					}
				}
			}
			dispatcher.send(message);
		}
		else if(targetMA.equals(Constants.MSG_TO_BASESTATION)){ //(destination is BASESTATION) send message to Basestation
			this.communicationChannelSender.send(message);
			
		}
		else {
			
			IAgentProxy proxy = getAgentProxy(targetMA);
			if (proxy!=null && !proxy.isMigrationExecuted()) {
				proxy.addEvent(message);
			}
			else {
				String address = this.naming.getAddress(targetMA);
				if (!address.equals("")) {
					try {
						message.setParam(ParamsLabel.EVT_ADDRESS, address);
						this.communicationChannelSender.send(message);
					}
					catch (CharNotValidException e) {
						LedsManager.error(LEDColor.CYAN);
						e.printStackTrace();
					}
				}
				else {	
					if (proxy != null ) {
						if (proxy.isMigrationExecuted()) {
							try {
								message.setParam(ParamsLabel.EVT_ADDRESS, proxy.getAgentAddress());
								this.communicationChannelSender.send(message);
							}
							catch (CharNotValidException e) {
								LedsManager.error(LEDColor.CYAN);
								e.printStackTrace();
							}
						}								
					}	
					else {
						Isolate agent = getAgent(targetMA);
						if ( agent != null) {
							dispatcher.send(message);
						}
					}
				}
			}
		}
		
	}
	
	public void sense(String sourceMA, Event backEvent) {
		this.sensorManager.handleEvent(sourceMA, backEvent);
	}
	
	public void actuate(String sourceMA, Event backEvent) {
		this.ioManager.handleEvent(sourceMA, backEvent);
	}

	public void input (String sourceMA, Event backEvent) {
		this.ioManager.handleEvent(sourceMA, backEvent);
	}
	
	public void flash (String sourceMA, Event backEvent) {
		this.ioManager.handleEvent(sourceMA, backEvent);
	}
	
	public String setTimer(String sourceMA, boolean periodic,
			long timeout, Event backEvent) {
		return this.timerManager.setTimer(periodic, timeout, backEvent);
	}

	public void resetTimer(String sourceID, String timerID) {
		
		this.timerManager.resetTimer(timerID);		
	}
	
	public void addAgent(Isolate agent) {
		try {
			this.agents.addElement(agent);
			Event refreshEvent = new Event (this.executionEngineName, Constants.BROADCAST, 
					Event.DSC_REFRESH, Event.NOW);
			refreshEvent.setParam(ParamsLabel.AGT_NAME, agent.getMainClassArguments()[0]);
			this.communicationChannelSender.send(refreshEvent);
		}
		catch (CharNotValidException e) {
			LedsManager.error(LEDColor.CYAN);
			e.printStackTrace();
		}
		
	}
	
	public Isolate getAgent(String agentID) {
		
		Isolate tempAgent = null;
		for (int i = 0; i < this.agents.size(); i++) {
			tempAgent = (Isolate)this.agents.elementAt(i);
			String agentName = tempAgent.getMainClassArguments()[0];
			if (agentName.equals(agentID))
				return tempAgent;
		}		
		return null;
	}
	
	public Vector getRemoteAgentsID() {
		return this.naming.getNeighborsAgents();
	}
	
	public IAgentProxy getAgentProxy(String agentID) {
		
		IAgentProxy tempAgentProxy = null;
		for (int i = 0; i < this.proxy.size(); i++) {
			tempAgentProxy = (IAgentProxy)this.proxy.elementAt(i);			
			if (agentID.equals(tempAgentProxy.getAgentID()))
				return tempAgentProxy;
		}		
		return null;
	}

	public boolean removeAgent(String sourceMA) {
		
		Isolate temp = null; 
		boolean found = false;
		for (int i = 0; i < this.agents.size() && !found; i++) {
			temp = (Isolate)this.agents.elementAt(i);
			String agentName = temp.getMainClassArguments()[0];
			if (agentName.equals(sourceMA))	{
				this.agents.removeElementAt(i);
				temp.exit(-1);
				temp = null;
				found = true;
			}
		}
		Runtime.getRuntime().gc();
		return found;
	}
	
	public void startAgent (String agentName) {
		try {
			RequestSender agent	= RequestSender.lookup(agentName);
			Event startEvent = new Event(this.executionEngineName, agentName, 
					Event.AGN_START, Event.NOW);
			ReplyEnvelope reply = agent.send(new ReceiveEvent(startEvent));
			reply.checkForRuntimeException();			
		}
		catch (NoSuchMailboxException e) {
			LedsManager.error(LEDColor.CYAN);
			e.printStackTrace();
		}
	}
	
	public void terminateAgent (String agentName) {
	
		new TerminateAgentThread(this, agentName).start();
	}
	
	public void removeAgentProxy(String agentID) {
	
		IAgentProxy tempAgentProxy = null;
		for (int i = 0; i < this.proxy.size(); i++) {
			tempAgentProxy = (IAgentProxy)this.proxy.elementAt(i);			
			if (agentID.equals(tempAgentProxy.getAgentID())) {
				this.proxy.removeElementAt(i);
				return;
			}
		}		
	}
	
	public IMobileAgentCommunicationChannelSender getCommunicationChannel() {
		return this.communicationChannelSender;
	}

	public IMobileAgentMigrationManager getMigrationManger() {
		return this.migrationManager;
	}

	public IEEEAddress getMyIEEEAddress() {
		return this.myIEEEAddress;
	}
	
	public void addNeighbor(String address, Vector agents) {
		this.naming.addSpotAgents(address, agents);		
	}

	public String getExecutionEngineName() {
		return executionEngineName;
	}
	
	public void migrationExecuted(String agentID) {
		IAgentProxy agentProxy = this.getAgentProxy(agentID);
		Vector events = agentProxy.getEvents();
		this.communicationChannelSender.send(events);
		agentProxy.clearEvents();
		
	}
}
